﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Storage.EntityModels;
using Storage;
using System.Reflection;
using GeoRef;

namespace Search
{
    /// <summary>
    /// This class represents a geographic location with a tolerance radius
    /// </summary>
    public struct TripParameter
    {
        /// <summary>
        /// An empty TripParameter
        /// </summary>
        public static readonly TripParameter Empty;

        /// <summary>
        /// Initializes the Empty TripParamer
        /// </summary>
        static TripParameter(){
            Empty = new TripParameter(null, int.MinValue, false);
        }

        /// <summary>
        /// Determines if two TripParameter have equal values
        /// </summary>
        /// <param name="a">First TripParameter to compare</param>
        /// <param name="b">Second TripParameter to compare</param>
        /// <returns>True if a and b are equal</returns>
        public static bool operator ==(TripParameter a, TripParameter b)
        {
            return a.ExactMatch == b.ExactMatch && a.Point == b.Point && a.Radius == b.Radius;
        }
        /// <summary>
        /// Determines if two TripParameter haven't equal values
        /// </summary>
        /// <param name="a">First TripParameter to compare</param>
        /// <param name="b">Second TripParameter to compare</param>
        /// <returns>True if a and b are not equal</returns>
        public static bool operator !=(TripParameter a, TripParameter b)
        {
            return a.ExactMatch != b.ExactMatch || a.Point != b.Point || a.Radius != b.Radius;
        }

        /// <summary>
        /// Determines if an object is equal this instance. If obj is TripParameter
        /// it uses the static == comparator
        /// </summary>
        /// <param name="obj">The object to compare</param>
        /// <returns>True if obj is equal this instance</returns>
        public override bool Equals(object obj)
        {
            if (obj is TripParameter) return this == (TripParameter)obj;
            else return base.Equals(obj);
        }
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    

        /// <summary>
        /// The coords of the selected point
        /// </summary>
        public GeoPoint Point { get; set; }

        /// <summary>
        /// The tolerance radius in meters
        /// </summary>
        public int Radius { get; set; }

        /// <summary>
        /// A parameter that indicates if the search has to match only on trips 
        /// that starts/stops exactly whitin the specified area, or can match also the trips
        /// that passes through the area
        /// </summary>
        public bool ExactMatch { get; set; }

        /// <summary>
        /// Simple constructor that intialize all fields
        /// </summary>
        /// <param name="p">The point</param>
        /// <param name="r">The radius</param>
        /// <param name="e">The ExactMatch parameter</param>
        public TripParameter(GeoPoint p, int r, bool e) : this()
        {
            Point = p;
            Radius = r;
            ExactMatch = e;
            // millesimo commento

        }
    }

    /// <summary>
    /// This class represents a parameter for a User. It has a static generic method to create 
    /// instances. It performs privacy policy checks to prevent a search on hidden user fields
    /// </summary>
    public class UserParameter
    {

        private Expression function;
        private PropertyInfo propField;
        /// <summary>
        /// The generated expression to build the WHERE clause
        /// </summary>
        public Expression GetExpression(Expression param)
        {
            MemberExpression property = Expression.Property(param, propField);
            return Expression.Invoke(function, property);
        }

        /// <summary>
        /// Creates an instance of UserParameter performing all privacy policy checks to prevent
        /// a search on hidden user properties
        /// Example: <code>Create&lt;string&gt;("Name", n => n.ToLower().Contains("pippo"));</code>
        /// </summary>
        /// <typeparam name="T">The type of the property to search on</typeparam>
        /// <param name="field">The User property to search on. It must be the exact property
        /// name of Storage.EntityModels.User class (match case). Only Anonymizer covered fields
        /// are allowed.
        /// </param>
        /// <param name="function">The function that expresses the condition for searching. Examples:
        /// If T is String: <code>s => s.EndsWith("trip")</code>
        /// If T is DateTime?: <code>d =&gt; d.HasValue &amp;&amp; d &gt; new DateTime(2008, 4, 12)</code>
        /// </param>
        /// <returns>The instance if all checks succeeded</returns>
        /// <exception cref="System.ArgumentException">
        /// If privacy policy doesn't allow to search on the specified property or field is not a valid 
        /// User property.
        /// </exception>
        /// <exception cref="System.NotImplementedException">
        /// If privacy policies are not specified on Database
        /// </exception>
        public static UserParameter Create<T>(string field, Expression<Func<T, bool>> function)
        {
            UserParameter p = new UserParameter();
            TripLoaDataContext entities = Storage.StorageManager.Instance.Entities;
            PropertyInfo privacyField = null;
            PropertyInfo propField = null;
            try
            {
                propField = typeof(User).GetProperty(field);
                if (propField == null) throw new ArgumentException("Field not found in User class");
                //PATCH to catch the enums properties
                if (propField.IsDefined(typeof(DBField), false))
                {
                    string dbField = ((DBField)Attribute.GetCustomAttribute(propField, typeof(DBField))).Field;
                    propField = typeof(User).GetProperty(dbField, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);
                }

                //PATCH to catch all type of feedbacks
                string anonymizerField = field.EndsWith("Feedbacks", StringComparison.InvariantCultureIgnoreCase) ? "Feedbacks" : field;
                privacyField = typeof(FixedPrivacyLevels).GetProperty(anonymizerField);
                if (privacyField == null) throw new ArgumentException("Privacy policy doesn't allow to select this field");
            }
            catch (Exception e)
            {
                throw new ArgumentException(e.Message);
            }
            if (entities.FixedPrivacyLevels.Count<FixedPrivacyLevels>() == 0)
                throw new NotImplementedException("No FixedPrivacyLevel set in the system");


            FixedPrivacyLevels fixLevel = entities.FixedPrivacyLevels.First<FixedPrivacyLevels>();
            Storage.PrivacyLevel level = (Storage.PrivacyLevel)privacyField.GetValue(fixLevel, null);
            if (level != Storage.PrivacyLevel.High && level != Storage.PrivacyLevel.NotVisible)
                throw new ArgumentException("Privacy policy doesn't allow to select this field");
            else
            {
                p.function = function;
                p.propField = propField;
            }
            return p;
        }

        /// <summary>
        /// Private constructor, use static method UserParameter.&lt;create&gt;
        /// </summary>
        private UserParameter() { }

    }

    /// <summary>
    /// A class represents a parameter for a Trip property. It performs privacy policy to avoid 
    /// a search over Owner properties e Passengers property
    /// </summary>
    public class TripFieldParameter
    {
        private PropertyInfo propField;
        private Expression function;

        // TODO: to be verified based on Storage project
        public static readonly string USER_FIELD = "Owner";
        public static readonly string PASSENGER_FIELD = "Passengers";

        private TripFieldParameter() { }

        /// <summary>
        /// The generated expression to build the WHERE clause
        /// </summary>
        /// <param name="param">The ParameterExpression on which invoke the filter function</param>
        /// <returns></returns>
        public Expression GetExpression(ParameterExpression param)
        {
            MemberExpression property = Expression.Property(param, propField);
            return Expression.Invoke(this.function, property);
        }

        /// <summary>
        /// Creates an instance of TripFieldParameter performing all privacy policy checks to prevent
        /// a search on Owner properties e Passengers property
        /// Example: create&lt;DateTime?&gt;("DepartureDate", d => d.HasValue &amp;&amp; d >= new DateTime(2008, 12, 31);
        /// </summary>
        /// <typeparam name="T">The type of the property to search on</typeparam>
        /// <param name="field">The Trip property to search on. It must be the exact property
        /// name of Storage.EntityModels.Trip class (match case). Passengers and Owner aren't allowed.
        /// Use UserParameter instead.
        /// </param>
        /// <param name="function">The function that expresses the condition for searching. Examples:
        /// If T is String: s => s.EndsWith("trip");
        /// If T is DateTime?: d => d.HasValue &amp;&amp; d > new DateTime(2008, 12, 31);
        /// </param>
        /// <returns>The instance if all checks succeeded</returns>
        /// <exception cref="System.ArgumentException">
        /// If privacy policy doesn't allow to search on the specified property or field is not a valid 
        /// Trip property.
        /// </exception>
        public static TripFieldParameter Create<T>(string field, Expression<Func<T, bool>> function)
        {
            if (field.Equals(USER_FIELD)) throw new ArgumentException("Use UserParameter to filter on owner's trip");
            if (field.Equals(PASSENGER_FIELD)) throw new ArgumentException("Cannot search over Trip.Passengers collection, for privacy reasons");
            
            TripFieldParameter t = new TripFieldParameter();
            PropertyInfo propField = typeof(Trip).GetProperty(field);
            if (propField == null) throw new ArgumentException("Field not found in Trip class");

            t.function = function;
            t.propField = propField;

            return t;
        }
    }
}
